package main

import (
	"fmt"
	"os"
	"strconv"
	"unsafe"

	"github.com/mattn/go-gtk/gdkpixbuf"

	"github.com/mattn/go-gtk/gdk"
	"github.com/mattn/go-gtk/glib"
	"github.com/mattn/go-gtk/gtk"
)

//ChessWidge 控件结构体
type ChessWidge struct {
	window      *gtk.Window //主窗口
	buttonMin   *gtk.Button
	buttonClose *gtk.Button

	// chessBoardImage *gtk.Image
	// tableChessBorad *gtk.Table

	labelBlack *gtk.Label //显示黑子个数
	labelWhite *gtk.Label //显示白子个数
	labelTime  *gtk.Label //倒计时间显示
	imageBlack *gtk.Image //黑子image，用于提示该谁落子
	imageWhite *gtk.Image //白子image，用于提示该谁落子

	imageMin   *gtk.Image //最小化
	imageClose *gtk.Image //关闭

}

//ChessInfo 属性结构体
type ChessInfo struct {
	w, h int //窗口的宽高
	x, y int //鼠标点击的坐标（相对于窗口）

	gridW  int //棋盘水平方向一个格子的宽度
	gridH  int //棋盘水平方向一个格子的高度
	startX int //棋盘起点x坐标
	startY int //棋盘起点y坐标

	currentRole int //当前落子角色
	tipTimerID  int //定时器Id,用于实现落子落子一闪一闪效果
	leftTimerID int //定时器id，用于倒计时
	timeNum     int //倒计时间

	machineTimerID int //机器落子定时器，延迟1s落子

	chess [8][8]int //二维数组，标记棋盘棋子状态
}

//枚举，标志棋盘棋子状态
const (
	Empty = iota //当前棋盘格子没有子
	Black
	White
)

//ChessBoard Chess结构体
type ChessBoard struct {
	//匿名字段
	ChessWidge
	ChessInfo
}

// ButtonSetImageFromFile 设置图片
func ButtonSetImageFromFile(button *gtk.Button, filename string) {
	var w, h int
	// obj, ok := button.(*gtk.Button)
	// if ok == true {
	// 	obj.GetSizeRequest(&w, &h)
	// 	//
	// }
	button.GetSizeRequest(&w, &h)
	pixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale(filename, w-10, h-10, false)

	//
	image := gtk.NewImageFromPixbuf(pixbuf)
	button.SetImage(image)

	//释放资源
	pixbuf.Unref()

}

// ImageSetImageFromFile 设置图片
func ImageSetImageFromFile(image *gtk.Image, filename string) {
	var w, h int

	image.GetSizeRequest(&w, &h)
	pixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale(filename, w, h, false)

	//
	image.SetFromPixbuf(pixbuf)

	//释放资源
	pixbuf.Unref()

}

// CreateWindow 创建窗体
func (obj *ChessBoard) CreateWindow() *gtk.Window {
	builder := gtk.NewBuilder()
	builder.AddFromFile("chesstable.glade")

	//
	obj.window = gtk.WindowFromObject(builder.GetObject("window1"))
	obj.buttonMin = gtk.ButtonFromObject(builder.GetObject("buttonminus"))
	obj.buttonClose = gtk.ButtonFromObject(builder.GetObject("buttonclose"))

	obj.imageMin = gtk.ImageFromObject(builder.GetObject("imagemin"))     //image
	obj.imageClose = gtk.ImageFromObject(builder.GetObject("imageclose")) //image

	//以下两种方式中的任意一种都是不需要的，多此一举
	//直接在背景图上再画棋盘图
	//obj.chessBoardImage = gtk.ImageFromObject(builder.GetObject("imagechessboard"))
	//obj.tableChessBorad = gtk.TableFromObject(builder.GetObject("tableChessBorad"))

	obj.labelBlack = gtk.LabelFromObject(builder.GetObject("labelblack")) //标签
	obj.labelWhite = gtk.LabelFromObject(builder.GetObject("labelwhite"))
	obj.labelTime = gtk.LabelFromObject(builder.GetObject("labeltime"))
	obj.imageBlack = gtk.ImageFromObject(builder.GetObject("imageblack")) //image
	obj.imageWhite = gtk.ImageFromObject(builder.GetObject("imagewhite")) //image

	//设置属性
	obj.w = 960
	obj.h = 640
	obj.window.SetSizeRequest(obj.w, obj.h)
	obj.window.SetPosition(gtk.WIN_POS_CENTER)
	obj.window.SetAppPaintable(true)
	obj.window.SetDecorated(false)
	obj.window.SetIconFromFile("image/white.png")

	//鼠标事件
	obj.window.SetEvents(int(gdk.BUTTON_PRESS_MASK | gdk.BUTTON1_MOTION_MASK))

	//
	ButtonSetImageFromFile(obj.buttonClose, "image/close.png")
	ButtonSetImageFromFile(obj.buttonMin, "image/minus.png")
	obj.buttonClose.SetCanFocus(false)
	obj.buttonMin.SetCanFocus(false)

	//标签属性
	obj.labelBlack.ModifyFontSize(50) //修改字体大小
	obj.labelWhite.ModifyFontSize(50) //修改字体大小
	obj.labelTime.ModifyFontSize(40)  //修改字体大小

	//修改字体颜色为白色
	obj.labelBlack.ModifyFG(gtk.STATE_NORMAL, gdk.NewColor("white"))
	obj.labelWhite.ModifyFG(gtk.STATE_NORMAL, gdk.NewColor("white"))
	obj.labelTime.ModifyFG(gtk.STATE_NORMAL, gdk.NewColor("white"))

	//添加棋盘
	//ImageSetImageFromFile(obj.chessBoardImage, "image/chessborad.jpg")

	//提示的棋子
	ImageSetImageFromFile(obj.imageBlack, "image/black.png")
	ImageSetImageFromFile(obj.imageWhite, "image/white.png")

	ImageSetImageFromFile(obj.imageMin, "image/minus.png")
	ImageSetImageFromFile(obj.imageClose, "image/close.png")

	//棋盘格子尺寸信息
	obj.startX = 150
	obj.startY = 118
	obj.gridW = 56
	obj.gridH = 56

	return obj.window

}

// PaintEvent 绘图事件处理函数，"expose-event"的回调函数
func PaintEvent(ctx *glib.CallbackContext) {
	arg := ctx.Data()
	obj, ok := arg.(*ChessBoard)
	if false == ok {
		fmt.Println("arg.(*ChessBoard) error")
		return
	}

	//指定绘图区域，在窗口上绘图
	painter := obj.window.GetWindow().GetDrawable()
	gc := gdk.NewGC(painter)

	// 无效
	// painterzi := obj.chessBoardImage.GetWindow().GetDrawable()
	// gczi := gdk.NewGC(painterzi)

	// 无效
	// paintercb := obj.tableChessBorad.GetWindow().GetDrawable()
	// gccb := gdk.NewGC(paintercb)

	////设置背景图的pixbuf，其宽高和窗口一样，最后一个参数固定为false
	bgpixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale("./image/bg.png", obj.w, obj.h, false)

	//画图，画背景图
	painter.DrawPixbuf(gc, bgpixbuf, 0, 0, 0, 0, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)

	cbpixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale("./image/chessborad.jpg", 470, 470, false)
	painter.DrawPixbuf(gc, cbpixbuf, 0, 0, obj.startX-10, obj.startY-10, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)

	//设置黑白子图片pixbuf
	blackPixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale("image/black.png", obj.gridW, obj.gridH, false)
	whitePixbuf, _ := gdkpixbuf.NewPixbufFromFileAtScale("image/white.png", obj.gridW, obj.gridH, false)

	//画子
	for i := 0; i < 8; i++ {
		for j := 0; j < 8; j++ {
			if obj.chess[i][j] == Black { //画黑子
				fmt.Printf("(%d,%d)画黑子\n", i, j)
				painter.DrawPixbuf(gc, blackPixbuf, 0, 0, obj.startX+i*obj.gridW, obj.startY+j*obj.gridH, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)

			} else if obj.chess[i][j] == White { //画白子
				fmt.Printf("(%d,%d)画白子\n", i, j)
				painter.DrawPixbuf(gc, whitePixbuf, 0, 0, obj.startX+i*obj.gridW, obj.startY+j*obj.gridH, -1, -1, gdk.RGB_DITHER_NONE, 0, 0)
			}
		}
	}

	//
	bgpixbuf.Unref()
	cbpixbuf.Unref()
	blackPixbuf.Unref()
	whitePixbuf.Unref()

}

//JudgeResult 方法，统计黑白棋个数，胜负判断
func (obj *ChessBoard) JudgeResult() {
	var blackNum, whiteNum int //黑白子个数
	isOver := true             //标记游戏是否结束，true为结束，false为没有结束

	for i := 0; i < 8; i++ {
		for j := 0; j < 8; j++ {
			if obj.chess[i][j] == Black { //黑子
				blackNum++ //黑子累加
			} else if obj.chess[i][j] == White {
				whiteNum++ //白子累加
			}

			//判断黑白子在i, j位置，能否吃子，没有改变chess的标志位
			if isOver && (0 < obj.JudgeRule(i, j, Black, false) || 0 < obj.JudgeRule(i, j, White, false)) {
				isOver = false //只要能吃子，说明游戏还没有结束
			}

		}
	}

	//黑白棋个数显示
	obj.labelBlack.SetText(strconv.Itoa(blackNum))
	obj.labelWhite.SetText(strconv.Itoa(whiteNum))

	//fmt.Println("isOver = ", isOver)
	if isOver == false {
		return //游戏还没有结束，终止此函数
	}

	//如果游戏结束，中断定时器
	glib.TimeoutRemove(obj.tipTimerID)
	glib.TimeoutRemove(obj.leftTimerID)

	//判断胜负
	var str string
	if blackNum > whiteNum {
		str = "你(黑方)赢了！！！\n继续游戏，按\"是\""
	} else if blackNum < whiteNum {
		str = "机器人(白方)赢了！！！\n继续游戏，按\"是\""
	} else {
		str = "平局！！！\n继续游戏，按\"是\""
	}

	//弹出对话框
	dialog := gtk.NewMessageDialog(
		obj.window,         //指定父窗口
		gtk.DIALOG_MODAL,   //模态对话框
		gtk.MESSAGE_INFO,   //info类型
		gtk.BUTTONS_YES_NO, //默认按钮
		str)                //设置内容
	dialog.SetTitle("游戏结束")

	result := dialog.Run() //运行对话框
	if result == gtk.RESPONSE_YES {
		fmt.Println("按下yes")
		obj.InitChess()        //重新初始化数据，继续游戏
		obj.window.QueueDraw() //刷新绘图区域

	} else {
		fmt.Println("按下关闭按钮")
		//关闭程序
		gtk.MainQuit()
	}

	dialog.Destroy() //销毁对话框
}

//MachinePlay 函数，机器落子
func MachinePlay(obj *ChessBoard) {
	//先暂停定时器
	glib.TimeoutRemove(obj.machineTimerID)

	max, px, py := 0, -1, -1

	//找到能吃子的位置
	for i := 0; i < 8; i++ {
		for j := 0; j < 8; j++ {
			num := obj.JudgeRule(i, j, obj.currentRole, false) //判断能吃几个棋子
			if num > 0 {
				//先找4个角落，先占角
				if (i == 0 && j == 0) || (i == 7 && j == 0) || (i == 0 && j == 7) || (i == 7 && j == 7) {
					px, py = i, j
					goto End
				}

				//不能占角，找吃子最多的
				if max < num { //吃子个数最多的
					px, py, max = i, j, num
				}
			}
		}
	}

End:
	if px == -1 { //说明机器没有落子的地方
		obj.ChangeRole() //改变角色
		return
	}

	obj.JudgeRule(px, py, obj.currentRole, true) //机器吃子
	obj.window.QueueDraw()                       //刷新绘图区域
	obj.ChangeRole()                             //改变落子角色

}

//ChangeRole 方法，改变落子角色
func (obj *ChessBoard) ChangeRole() {
	//先隐藏提示图片
	obj.imageBlack.Hide()
	obj.imageWhite.Hide()

	//重设选手倒计时
	obj.timeNum = 10
	obj.labelTime.SetText(strconv.Itoa(obj.timeNum))

	if obj.currentRole == Black { //白子下
		obj.currentRole = White
	} else { //黑子下

		obj.currentRole = Black
	}

	//统计黑白棋个数，胜负判断
	obj.JudgeResult()

	if obj.currentRole == White { //机器落子，启动定时器
		obj.machineTimerID = glib.TimeoutAdd(3000, func() bool {
			MachinePlay(obj) //机器落子
			return true
		})
	}

}

//JudgeRule  吃子的规则
// 吃子规则的参数：棋盘数组坐标位置(x y) role 当前落子角色
// eatChess为true，代表改变原来的数组， false不改变数组内容，只判断此位置能吃多少个子
// 返回值：吃子个数
func (obj *ChessBoard) JudgeRule(x, y int, role int, eatChess bool) (eatNum int) {
	// 棋盘的八个方向
	dir := [8][2]int{{1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}}

	tempX, tempY := x, y // 临时保存棋盘数组坐标位置

	if obj.chess[tempX][tempY] != Empty { // 如果此方格内已有棋子，返回
		return 0
	}

	// 棋盘的8个方向
	for i := 0; i < 8; i++ {
		tempX += dir[i][0]
		tempY += dir[i][1] // 准备判断相邻棋子

		// 如果没有出界，且相邻棋子是对方棋子，才有吃子的可能．
		if (tempX < 8 && tempX >= 0 && tempY < 8 && tempY >= 0) && (obj.chess[tempX][tempY] != role) && (obj.chess[tempX][tempY] != Empty) {
			tempX += dir[i][0]
			tempY += dir[i][1] // 继续判断下一个，向前走一步
			for tempX < 8 && tempX >= 0 && tempY < 8 && tempY >= 0 {
				if obj.chess[tempX][tempY] == Empty { // 遇到空位跳出
					break
				}

				if obj.chess[tempX][tempY] == role { // 找到自己的棋子，代表可以吃子
					if eatChess == true { // 确定吃子
						obj.chess[x][y] = role // 开始点标志为自己的棋子
						tempX -= dir[i][0]
						tempY -= dir[i][1] // 后退一步
						for (tempX != x) || (tempY != y) {
							// 只要没有回到开始的位置就执行
							obj.chess[tempX][tempY] = role // 标志为自己的棋子
							tempX -= dir[i][0]
							tempY -= dir[i][1] // 继续后退一步
							eatNum++           // 累计
						}
					} else { //不吃子，只是判断这个位置能不能吃子
						tempX -= dir[i][0]
						tempY -= dir[i][1]                 // 后退一步
						for (tempX != x) || (tempY != y) { // 只计算可以吃子的个数
							tempX -= dir[i][0]
							tempY -= dir[i][1] // 继续后退一步
							eatNum++
						}
					}

					break // 跳出循环
				} // 没有找到自己的棋子，就向前走一步

				tempX += dir[i][0]
				tempY += dir[i][1] // 向前走一步
			}
		} // 如果这个方向不能吃子，就换一个方向
		tempX, tempY = x, y
	}

	return // 返回能吃子的个数
}

//MousePressEventHandel 处理鼠标事件
func MousePressEventHandel(ctx *glib.CallbackContext) {
	arg := ctx.Data()
	obj, ok := arg.(*ChessBoard) //类型断言
	if false == ok {
		fmt.Println("arg.(*ChessBoard) Error")
		return
	}

	//
	tmp := ctx.Args(0)
	event := *(**gdk.EventButton)(unsafe.Pointer(&tmp))
	if event.Button == 1 { //左键按下
		obj.x, obj.y = int(event.X), int(event.Y) //保存移动起点
		fmt.Println("event.X:", event.X, "\nevent.Y:", event.Y)
		if obj.currentRole == White {
			return
		}
		// 要保证点击点在棋盘范围里面
		if obj.x >= obj.startX && obj.x <= obj.startX+8*obj.gridW && obj.y >= obj.startY && obj.y <= obj.startX+8*obj.gridH {
			// 棋盘的位置转换转换为坐标下标值
			i := (obj.x - obj.startX) / obj.gridW
			j := (obj.y - obj.startY) / obj.gridH

			//保证i, j在0~7范围里
			if i >= 0 && i <= 7 && j >= 0 && j <= 7 {

				if obj.JudgeRule(i, j, obj.currentRole, true) > 0 {
					obj.chess[i][j] = obj.currentRole //点击的位置标志为obj.currentRole
					obj.window.QueueDraw()            //刷新绘图区域
					obj.ChangeRole()
				} else {
					fmt.Printf("i = %d, j = %d,不能吃子\n", i, j)
				}

			}
		}

	}

}

//HandleSignal 处理信号
func (obj *ChessBoard) HandleSignal() {
	//
	obj.window.Connect("button-press-event", MousePressEventHandel, obj)

	//
	obj.window.Connect("motion-notify-event", func(ctx *glib.CallbackContext) {
		arg := ctx.Args(0)

		event := *(**gdk.EventButton)(unsafe.Pointer(&arg))

		//移动窗口
		obj.window.Move(int(event.XRoot)-obj.x, int(event.YRoot)-obj.y)

		//fmt.Println("event.XRoot:", event.XRoot, "\nevent.YRoot:", event.YRoot)

	})

	//改变窗口大小时，触发"configure-event"，然后手动刷新绘图区域，否则图片会重叠
	obj.window.Connect("configure-event", func() {

		obj.window.QueueDraw() //刷新绘图区域
	})

	//绘图（曝光）事件，回调函数PaintEvent做绘图操作，把obj传给回调函数
	obj.window.Connect("expose-event", PaintEvent, obj)

	obj.buttonMin.Connect("clicked", func() {
		obj.window.Iconify() //窗口最小化
	})

	obj.buttonClose.Connect("clicked", func() {
		gtk.MainQuit() //关闭程序
	})

	obj.imageMin.Connect("clicked", func() {
		obj.window.Iconify() //窗口最小化
	})

	obj.imageClose.Connect("clicked", func() {
		gtk.MainQuit() //关闭程序
	})
}

//ShowTip 实现落子落子一闪一闪效果
func ShowTip(obj *ChessBoard) {
	if obj.currentRole == Black {
		//
		obj.imageWhite.Hide()
		if obj.imageBlack.GetVisible() == true {
			obj.imageBlack.Hide()
		} else {
			obj.imageBlack.Show()
		}
	} else if obj.currentRole == White {
		//
		obj.imageBlack.Hide()
		if obj.imageWhite.GetVisible() == true {
			obj.imageWhite.Hide()
		} else {
			obj.imageWhite.Show()
		}
	}
}

// InitChess 棋局初始化
func (obj *ChessBoard) InitChess() {
	//
	//obj.currentRole = White
	obj.currentRole = Black
	//
	obj.imageBlack.Hide()
	obj.imageWhite.Hide()

	//全部标记为Empty
	for i := 0; i < 8; i++ {
		for j := 0; j < 8; j++ {
			obj.chess[i][j] = Empty
		}
	}

	//
	//中间位置
	obj.chess[3][3] = Black
	obj.chess[4][4] = Black
	obj.chess[4][3] = White
	obj.chess[3][4] = White

	//落子提示
	obj.tipTimerID = glib.TimeoutAdd(500, func() bool {
		ShowTip(obj)
		return true
	})

	//初始棋子个数
	obj.labelBlack.SetText(strconv.Itoa(2))
	obj.labelWhite.SetText(strconv.Itoa(2))

	//选手倒计时
	obj.timeNum = 10
	obj.labelTime.SetText(strconv.Itoa(obj.timeNum))

	//启动倒计时定时器
	obj.leftTimerID = glib.TimeoutAdd(1000, func() bool {
		obj.timeNum--
		obj.labelTime.SetText(strconv.Itoa(obj.timeNum))
		if obj.timeNum == 0 {
			obj.ChangeRole()
		}
		return true
	})

}

func main() {
	gtk.Init(&os.Args)

	var obj ChessBoard

	window := obj.CreateWindow()
	window.Show()

	//事件,信号处理
	obj.HandleSignal()

	//初始化
	obj.InitChess()

	gtk.Main()

}
